/**
 * edu.utexas.mckoi.store.NIOBufferedFile  28 Jan 2003
 *
 * Mckoi SQL Database ( http://www.mckoi.com/database )
 * Copyright (C) 2000, 2001, 2002  Diehl and Associates, Inc.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * Version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License Version 2 for more details.
 *
 * You should have received a copy of the GNU General Public License
 * Version 2 along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * Change Log:
 * 
 * 
 */

package mckoi.store;

import java.io.RandomAccessFile;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.MappedByteBuffer;

/**
 * An implementation of AbstractBufferedFile that uses the NIO API to map
 * pages of the underlying file into memory.
 *
 * @author Tobias Downer
 */

class NIOBufferedFile extends AbstractBufferedFile {

  /**
   * The FileChannel object.
   */
  private FileChannel access_file_channel;

  /**
   * The mode of the mapped byte buffer.
   */
  private FileChannel.MapMode map_mode;

  /**
   * The current size of the file.
   */
  private long file_size;
  

  /**
   * The constructor.
   */
  public NIOBufferedFile(int unique_id, RandomAccessFile file, String mode,
                         BufferManager manager) throws IOException {
    super(unique_id, file, manager);
    if (mode.equals("r")) {
      map_mode = FileChannel.MapMode.READ_ONLY;
    }
    else if (mode.equals("rw")) {
      map_mode = FileChannel.MapMode.READ_WRITE;
    }
    else {
      throw new RuntimeException("Unrecognised access mode.");
    }
    file_size = file.length();
    access_file_channel = file.getChannel();
  }

  // ---------- Implemented from AbstractBufferedFile ----------

  public PageBuffer createPageFor(long position, int len) {
    return new NIOPageBuffer(position, len);
  }

  public void sizeChange(long old_size, long new_size) throws IOException {
    file_size = new_size;
    // If the store has a page buffer created over the given area, we
    // flush and reinitialize the page.
    NIOPageBuffer page_buffer =
                  (NIOPageBuffer) getPageFromCache((old_size - 1) / page_size);
    if (page_buffer != null) {
      page_buffer.flushImpl(-1, -1);
      page_buffer.initImpl();
    }
  }

  // ---------- Inner classes ----------

  /**
   * This implementation of a page buffer initializes the page by reading the
   * page into a byte[] array in memory.
   */
  class NIOPageBuffer extends PageBuffer {
    
    private long position;
    private int length;
    private MappedByteBuffer buffer;

    public NIOPageBuffer(long position, int length) {
      this.position = position;
      this.length = length;
    }

    public void initImpl() throws IOException {
      int map_size = (int) Math.min(file_size - position, (long) length);
      if (map_size < 0) {
        System.out.println("length = " + length);
        System.out.println("file_size = " + file_size);
        System.out.println("position = " + position);
      }
      buffer = access_file_channel.map(map_mode, position, map_size);
    }

    public byte readImpl(int position) {
      return buffer.get(position);
    }

    public void readImpl(int position, byte[] b, int off, int len) {
      buffer.position(position);
      buffer.get(b, off, len);
    }

    public void writeImpl(int position, byte b) {
      buffer.put(position, b);
    }

    public void writeImpl(int position, byte[] b, int off, int len) {
      buffer.position(position);
      buffer.put(b, off, len);
    }

    public void flushImpl(int p1, int p2) throws IOException {
      buffer.force();
    }

    public void disposeImpl() {
      buffer = null;
    }

  }

}

